package devserv

import (
	"codedef"
	"config"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"github.com/gorilla/websocket"
	"github.com/satori/go.uuid"
	"log"
	"message"
	"net/http"
	"rhelper"
	"strconv"
	"strings"
	"time"
)

var (
	upgrader = websocket.Upgrader{ReadBufferSize: 1024,
		WriteBufferSize: 1024}
	controllers = make(map[string]*Controller)
	myMux       = &myHttpServerMux{}

	// WaitKey = make(map[string]chan int)
	// leftOnWait = make(map[string]chan bool)
)

type myHttpServerMux struct {
}

func (mux *myHttpServerMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var path = r.URL.Path
	log.Println("path: ", path)
	if path == "/gnws" || path == "/gnwss" {
		acceptWebsocket(w, r)
		return
	} else {
		log.Printf("no handler for Request, path:%s\n", path)
		w.WriteHeader(404)
		return
	}
}

func CreateHttpServer() {
	go acceptHttpRequest()

	go acceptHttpsRequest()
}

func acceptHttpRequest() {
	blPortStr := fmt.Sprintf(":%d", config.DServerPort)
	s := &http.Server{
		Addr:    blPortStr,
		Handler: myMux,
		// ReadTimeout:    10 * time.Second,
		//WriteTimeout:   120 * time.Second,
		MaxHeaderBytes: 1 << 8,
	}

	log.Printf("Http server listen at:%d\n", config.DServerPort)

	err := s.ListenAndServe()
	if err != nil {
		log.Println("Http server ListenAndServe failed:", err)
	}
}

func acceptHttpsRequest() {
	blPortStr := fmt.Sprintf(":%d", config.DServerPort+2)
	s := &http.Server{
		Addr:    blPortStr,
		Handler: myMux,
		// ReadTimeout:    10 * time.Second,
		//WriteTimeout:   120 * time.Second,
		MaxHeaderBytes: 1 << 8,
	}

	certPath := fmt.Sprintf("%s%s", config.CertificatesDirectory, "/devCert.pem")
	keyPath := fmt.Sprintf("%s%s", config.CertificatesDirectory, "/devKey.pem")

	log.Printf("Https server listen at:%d\n", config.DServerPort+2)

	err := s.ListenAndServeTLS(certPath, keyPath)
	if err != nil {
		log.Println("Https server ListenAndServeTLS failed:", err)
	}
}

type ReplyWithDateTime struct {
	URI          string `json:"uri"`
	Error        int    `json:"error"`
	Weekday      int    `json:"wd"`
	Year         int    `json:"year"`
	Month        int    `json:"month"`
	Day          int    `json:"day"`
	MilliSeconds int    `json:"ms"`
}

type LoginResult struct {
	URI          string `json:"uri"`
	Error        int    `json:"error"`
	Weekday      int    `json:"wd"`
	Year         int    `json:"year"`
	Month        int    `json:"month"`
	Day          int    `json:"day"`
	MilliSeconds int    `json:"ms"`

	GuidHighH uint32 `json:"hh"`
	GuidHighL uint32 `json:"hl"`
	GuidLowH  uint32 `json:"lh"`
	GuidLowL  uint32 `json:"ll"`
}

type DeviceDateTime struct {
	Error        int
	Weekday      int
	Year         int
	Month        int
	Day          int
	MilliSeconds int
}

func getCurrentDateTime() *DeviceDateTime {
	dt := DeviceDateTime{}
	now := time.Now()
	nowUTC := now.UTC()
	dt.Weekday = int(nowUTC.Weekday())
	dt.Month = int(nowUTC.Month())
	dt.Year = nowUTC.Year()
	dt.Day = nowUTC.Day()

	middleNigh := time.Date(dt.Year, time.Month(dt.Month),
		dt.Day, 0, 0, 0, 0, time.UTC)
	d := (nowUTC.Sub(middleNigh))
	dt.MilliSeconds = int(d.Nanoseconds() / 1000000)

	return &dt
}

func acceptWebsocket(w http.ResponseWriter, r *http.Request) {
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}

	controller := newController(ws)
	if controller == nil {
		ws.Close()
		return
	}

	var reply = LoginResult{Error: 0, URI: "/loginReply"}
	if controller.Id == "" {
		u1 := uuid.NewV4()
		bb := u1.Bytes()
		reply.GuidHighH = binary.LittleEndian.Uint32(bb[12:])
		reply.GuidHighL = binary.LittleEndian.Uint32(bb[8:12])
		reply.GuidLowH = binary.LittleEndian.Uint32(bb[4:8])
		reply.GuidLowL = binary.LittleEndian.Uint32(bb[0:4])

		// new UUID
		controller.Id = fmt.Sprintf("%s", u1)
	}

	//ch, isCfg := WaitKey[controller.Account]

	accountFromDB, _ := controller.loadAccountFromDB()
	// if controller.Key == 0 && accountFromDB == "" {
	// 	log.Println("controller was delete, can't connect to server again")

	// 	reply.Error = codedef.ERR_CONTROLLER_WAS_DELETE
	// 	ws.WriteJSON(reply)
	// 	ws.Close()
	// 	return
	// }

	//type uriMsg struct {
	//	Uri string `json:"uri"`
	//}
	//
	//type pubMsg struct {
	//	Cid string `json:"cid"`
	//	ErrCode int `json:"errCode"`
	//	MsgBuf []byte `json:"msgBuf"`
	//}
	//urimsg := uriMsg{}
	//urimsg.Uri = "login"
	//urimsgJson, _ := json.Marshal(urimsg)
	//
	//pubmsg := pubMsg{}
	//pubmsg.Cid = controller.Id
	//pubmsg.ErrCode = 0
	//pubmsg.MsgBuf = urimsgJson
	//pubmsgJson , _ := json.Marshal(pubmsg)
	//publish("purifiersUserServer",pubmsgJson)

	if controller.Key != 0 && accountFromDB != controller.Account {
		deviceName, err := deleteDeviceFromOriginAccount(controller.Id, accountFromDB)
		if err == nil {
			go notifyOriginAccountDeviceWasDelete(accountFromDB, controller.Account, controller.Id, deviceName)
		}
	}

	log.Println(controller)

	remoteAddr := r.RemoteAddr
	addrs := strings.Split(remoteAddr, ":")
	var ip string
	if len(addrs) > 0 {
		ip = addrs[0]
	}
	controller.save2DB(ip)

	dt := getCurrentDateTime()
	reply.Year = dt.Year
	reply.Month = dt.Month
	reply.Day = dt.Day
	reply.Weekday = dt.Weekday
	reply.MilliSeconds = dt.MilliSeconds
	ws.WriteJSON(reply)

	a, err := json.Marshal(reply)
	log.Println("reply,", string(a))
	//}

	id := controller.Id

	older, ok := controllers[id]
	if ok {
		//sendMessageToOldWS(controller.Account)
		log.Println("duplicate controller websocket, close old one")
		older.ws.Close()
		<-older.chanx
	}
	//sendDeviceStatus(controller.Account, controller.Id, "online")
	// controller.add2OnlineDev()
	controllers[id] = controller

	log.Println("accept a controller websocket")

	keepAliveC := make(chan int)
	go keepAliveWS(ws, keepAliveC, id)

	// if config.LatestFirmVersionCode > controller.FirmVersionCode && (controller.Relay == 0) {
	// 	go updateFirm(id)
	// }

	controller.add2OnlineDev()
	if controller.Key != 0 {
		controller.onWaitKeyResp()
		controller.Key = 0
	}

	for {
		messageType, message, err := ws.ReadMessage()

		if err != nil {
			log.Println("controller websocket receive error:", err)
			keepAliveC <- 2
			break
		}

		if messageType == websocket.BinaryMessage {
			log.Println("receive controller binary message:", message)
			continue
		}

		if message != nil && len(message) > 0 {
			dispatchControllerMsg(controller, message)
		}

		log.Printf("receive device %s message:%s\n", id, message)
		keepAliveC <- 1
	}

	log.Println("close websocket")
	ws.Close()

	controller.setDeviceOfflineTime()
	removeDevFromOnline(id)
	//sendDeviceStatus(controller.Account, controller.Id, "offline")

	controller.chanx <- true
	close(controller.chanx)
	close(keepAliveC)
	delete(controllers, id)
}

func keepAliveWS(ws *websocket.Conn, c <-chan int, cid string) {
	var flag int = 0
	var count int = 0
	ticker := time.NewTicker(3 * time.Minute)
	for {
		select {
		case <-ticker.C:
			if flag == 0 {
				//log.Println("Send keep-alive message to device")
				_ = ws.WriteMessage(websocket.TextMessage, []byte("{\"uri\":\"/ka\"}"))
			} else {
				flag = 0
				//log.Println("flag not 0, skip keep-alive msg")
			}

			log.Println("count: ", count)

			if count >= 1 {
				sendDeviceStatus(cid)
				ws.Close()
			} else {
				count = count + 1
			}

		case flag = <-c:
			if flag == 2 {
				log.Println("Keep-alive abort")
				ticker.Stop()
				return
			}

			if flag == 1 {
				log.Println("clear count")
				count = 0
			}
		}
	}
}

func newController(ws *websocket.Conn) *Controller {
	var controller = &Controller{}

	err := ws.ReadJSON(controller)
	if err != nil {
		log.Println("read controller first json message failed:", err)
		return nil
	}

	if controller.Account == "" {
		log.Println("controller has no account binding, can't access server, cid: ", controller.Id)
		return nil
	}

	if controller.createDispatcher() == false {
		log.Println("can't create dispatcher for controller")
		return nil
	}

	controller.ws = ws
	controller.chanx = make(chan bool, 1)
	return controller
}

func dispatchControllerMsg(controller *Controller, message []byte) {
	msgHead := &Dev2ServMsgHead{}
	err := json.Unmarshal(message, msgHead)
	if err != nil {
		log.Println(string(message))
		log.Println("unmarshal json message head failed:", err)
		return
	}

	controller.onJSONMessage(msgHead, message)
}

func GetControllerById(id string) *Controller {
	c, ok := controllers[id]
	if ok == false {
		return nil
	}
	return c
}

func sendDeviceStatus(deviceId string) {
	rconn := rhelper.Pool().Get()
	defer rconn.Close()

	fields, err := redis.Strings(rconn.Do("HMGET", rhelper.ControllerHash+deviceId, "alias", "deviceName", "account"))
	if err != nil && err != redis.ErrNil {
		log.Println("sendDeviceStatus, get account controller string error:", err)
		return
	}

	deviceName := fields[0]
	if deviceName == "" {
		deviceName = fields[1]
	}

	msg := message.Message{}
	msg.Type = "evsystem"
	msg.To = fields[2]
	msg.From = deviceName
	msg.Abstract = ""
	msg.Detail = deviceName + " " + deviceId + " is offline"
	msg.Title = "offline"
	msg.FromName = deviceName

	if msg.To == "" {
		return
	}

	message.SendMessage(msg, deviceId)
	//.messageSendMessage(msg)
}

func updateFirm(cid string) {
	log.Println("after 10 minute will updateFirm")
	time.Sleep(10 * time.Second)

	type UpgradeRequest struct {
		URI        string `json:"uri"`
		URL        string `json:"url"`
		NewVersion string `json:"newVersion"`
	}

	var ur = &UpgradeRequest{}
	ur.URI = "/upgrade"
	ur.NewVersion = config.LatestFirmVersion
	ur.URL = config.FirmDownloadUrl
	c, ok := controllers[cid]
	if ok == false {
		return
	}

	log.Printf("update %s firm", cid)

	if (config.LatestFirmVersionCode > c.FirmVersionCode) && (c.Relay == 0) {
		c.WriteJSONWait(ur, nil)
	}
}

func deleteDeviceFromOriginAccount(cid string, originAccount string) (string, error) {
	if originAccount == "" {
		return "", fmt.Errorf("originAccount can't be empty")
	}

	rconn := rhelper.Pool().Get()
	defer rconn.Close()

	uniqueId, err := redis.String(rconn.Do("HGET", rhelper.AccountHash+originAccount, "id"))
	if err != nil || uniqueId == "" {
		log.Printf("deleteDeviceFromOriginAccount, get user unique id failed:%s,account:%s\n", err.Error(), originAccount)
		return "", err
	}

	accControllerStr, err := redis.String(rconn.Do("HGET", rhelper.UserHash+uniqueId, "controllers"))
	if err != nil {
		log.Println("delete device from origin account failed, error:", err)
		return "", err
	}

	accControllerStr = rhelper.CommaStringDelete(accControllerStr, cid)

	fields, err := redis.Strings(rconn.Do("HMGET", rhelper.ControllerHash+cid, "alias", "deviceName"))
	if err != nil && err != redis.ErrNil {
		log.Println("notifyOriginAccountDeviceWasDelete, get account controller string error:", err)
		return "", err
	}

	deviceName := fields[0]
	if deviceName == "" {
		deviceName = fields[1]
	}

	rconn.Send("MULTI")
	rconn.Send("HSET", rhelper.UserHash+uniqueId, "controllers", accControllerStr)
	rconn.Send("Hdel", rhelper.ControllerHash+cid, "alias", "remark")
	_, err = rconn.Do("EXEC")
	if err != nil {
		log.Println("deleteDeviceFromOriginAccount, save controller data error:", err)
	}

	return deviceName, nil
}

func notifyOriginAccountDeviceWasDelete(originAccount string, currentAccount string, cid string, deviceName string) {
	if originAccount == "" {
		return
	}

	msg := message.Message{}
	msg.Type = "evsystem"
	msg.To = originAccount
	msg.From = deviceName
	msg.Abstract = "addDev"
	msg.Detail = deviceName + ":" + cid + " added by " + currentAccount
	msg.Title = "Device added by another account"

	message.SendMessage(msg, cid)
}

func parserUserRequestHead(msgBuf []byte) string {
	var userRequestHead UserRequestMsgHead
	err := json.Unmarshal(msgBuf, &userRequestHead)
	if err != nil {
		log.Println("onUserRequest failed, json decode failed:", err)
		return ""
	}

	return userRequestHead.URI
}

func OnUserRequest(msgBuf []byte) {
	var userRequest UserRequest
	err := json.Unmarshal(msgBuf, &userRequest)
	if err != nil {
		log.Println("onUserRequest failed, json decode failed:", err)
		return
	}

	cid := userRequest.Cid
	c := GetControllerById(cid)
	if c == nil {
		//reply controller offline error
		log.Println("controller is offline, id: ", cid)
		removeDevFromOnline(cid)
		uri := parserUserRequestHead(userRequest.MsgBuf)
		replyError(userRequest.UserServer, cid, codedef.ERR_CONTROLLER_OFFLINE, uri)
		return
	}

	// serverCmd same as close webSocket, give up wait
	if userRequest.ServerCmd != "" {
		log.Println("User request, serverCmd: ", userRequest.ServerCmd)
		c.onUserRequestWithServerCmd(&userRequest)
		return
	}

	if c.IsBusy() {
		//reply controller offline error
		log.Println("controller is busy, id: ", cid)
		uri := parserUserRequestHead(userRequest.MsgBuf)
		replyError(userRequest.UserServer, cid, codedef.ERR_CONTROLLER_BUSY, uri)
		return
	}

	wait := make(chan []byte, 1)

	c.onUserRequest(userRequest.MsgBuf, wait)

	select {
	case reply := <-wait:
		type errMsg struct {
			Uri   string `json:"uri"`
			Error string `json:"error"`
		}

		em := errMsg{}
		json.Unmarshal(reply, &em)
		if em.Error != "0" && strings.Replace(em.Error, " ", "", -1) != "" {
			error_int, _ := strconv.Atoi(em.Error)
			replyError(userRequest.UserServer, cid, error_int, em.Uri)
		}
		publishMessage(userRequest.UserServer, c.Id, reply)

		setLeftOnNotifyIfRelayOpen(userRequest.MsgBuf, c.Id)
		break
	case <-time.After(time.Second * 120):
		c.GiveupWaitAndDiscard()
		log.Println("user request device timeout")
		//reply rquest time out error
		break
	}

}

func removeDevFromOnline(cid string) {
	rconn := rhelper.Pool().Get()
	defer rconn.Close()

	_, err := rconn.Do("SREM", rhelper.OnlineDev, cid)
	if err != nil {
		log.Println("removeDevFromOnline failed, error:", err)
	}

}

func ClearOnlineDev() {
	if len(controllers) > 0 {
		return
	}

	rconn := rhelper.Pool().Get()
	defer rconn.Close()

	rconn.Do("del", rhelper.OnlineDev)
}

func setKeyExpire() {
	rconn := rhelper.Pool().Get()
	defer rconn.Close()

	// set key expire 150 s
	_, err := rconn.Do("EXPIRE", rhelper.OnlineDev, 150)
	if err != nil {
		log.Println("setKeyExpire failed, error:", err)
	}
}

func SetKeyExpire() {
	ticker := time.NewTicker(2 * time.Minute)
	for {
		select {
		case <-ticker.C:
			setKeyExpire()
		}
	}
}
